package com.tianji.learning.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.api.client.user.UserClient;
import com.tianji.api.dto.user.UserDTO;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.constants.LearningConstants;
import com.tianji.learning.constants.RedisConstants;
import com.tianji.learning.domain.po.PointsBoard;
import com.tianji.learning.domain.query.PointsBoardQuery;
import com.tianji.learning.domain.vo.PointsBoardItemVO;
import com.tianji.learning.domain.vo.PointsBoardVO;
import com.tianji.learning.mapper.PointsBoardMapper;
import com.tianji.learning.service.IPointsBoardService;
import groovy.util.logging.Slf4j;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 学霸天梯榜 服务实现类
 * </p>
 *
 * @author elliehy
 * @since 2024-06-07
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class PointsBoardServiceImpl extends ServiceImpl<PointsBoardMapper, PointsBoard> implements IPointsBoardService {
	private final StringRedisTemplate redisTemplate;
	private final UserClient userClient;
	
	@Override
	public PointsBoardVO queryPointsBoardBySeason(PointsBoardQuery query) {
		//1.获取当前登录用户ID
		Long userId = UserContext.getUser();
		//2.判断是查询当前赛季还是历史赛季
		//2.1凭借KEY
		LocalDateTime now = LocalDateTime.now();
		String format = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
		String key = RedisConstants.POINTS_BOARD_KEY_PREFIX + format;
		Long season = query.getSeason();
		//2.2  season是0或者null代表当前赛季
		boolean isCurrent = season == null || season == 0;
		//3.查询我的排名和积分(根据第2步) query.season  判断是redis还是Db
		PointsBoard board = isCurrent ? queryMyCurrentBoard(key, userId) : queryMyHistoryBoard(season);
		
		//4.查询赛季列表  根据query.season  判断是查redis还是Db
		List<PointsBoard> list = isCurrent ? queryCurrentBoard(key, query.getPageSize(), query.getPageNo()) :
				queryHistoryBoard(query);
		// 5. 创建并初始化PointsBoardVO
		PointsBoardVO vo = new PointsBoardVO();
		
		// 5.1. 处理我的信息
		Optional.ofNullable(board)
				.ifPresent(b -> {
					vo.setPoints(b.getPoints());
					vo.setRank(b.getRank());
				});
		
		// 5.2. 查询用户信息
		List<PointsBoard> boards = Optional.ofNullable(list)
				.orElse(Collections.emptyList());
		Set<Long> uIds = boards.stream()
				.map(PointsBoard::getUserId)
				.collect(Collectors.toSet());
		List<UserDTO> users = userClient.queryUserByIds(uIds);
		Map<Long, String> userMap = Optional.ofNullable(users)
				.orElse(Collections.emptyList())
				.stream().collect(Collectors.toMap(UserDTO::getId, UserDTO::getName));
		
		// 5.3. 转换为PointsBoardItemVO
		List<PointsBoardItemVO> items = boards.stream()
				.map(p -> {
					PointsBoardItemVO item = new PointsBoardItemVO();
					item.setPoints(p.getPoints());
					item.setRank(p.getRank());
					item.setName(userMap.getOrDefault(p.getUserId(), ""));
					return item;
				})
				.collect(Collectors.toList());
		vo.setBoardList(items);
		return vo;
	}
	
	@Override
	public void createPointsBoardTableBySeason(Integer season) {
		getBaseMapper().createPointsBoardTable(LearningConstants.POINTS_BOARD_TABLE_PREFIX + season);
	}
	
	@Override
	public List<PointsBoard> queryCurrentBoardList(String key, int pageNo, int pageSize) {
		// 1.计算分页
		int from = (pageNo - 1) * pageSize;
		// 2.查询
		Set<ZSetOperations.TypedTuple<String>> tuples = redisTemplate.opsForZSet()
				.reverseRangeWithScores(key, from, from + pageSize - 1);
		if (CollUtils.isEmpty(tuples)) {
			return CollUtils.emptyList();
		}
		// 3.封装
		int rank = from + 1;
		List<PointsBoard> list = new ArrayList<>(tuples.size());
		for (ZSetOperations.TypedTuple<String> tuple : tuples) {
			String userId = tuple.getValue();
			Double points = tuple.getScore();
			if (userId == null || points == null) {
				continue;
			}
			PointsBoard p = new PointsBoard();
			p.setUserId(Long.valueOf(userId));
			p.setPoints(points.intValue());
			p.setRank(rank++);
			list.add(p);
		}
		return list;
	}
	
	/**
	 * 查询当前赛季的积分排名
	 * @param key      保存在Redis中的KEY
	 * @param pageSize 分页参数之分页的长度
	 * @param pageNo   分页参数之分页的页码
	 * @return 当前赛季的积分排名
	 */
	private List<PointsBoard> queryCurrentBoard(String key, Integer pageSize, Integer pageNo) {
		//1.计算start和stop 分页值
		int start = (pageNo - 1) * pageSize;
		int stop = start + pageSize - 1;
		//2.利用zRevRange名 会按分数倒序 分页查询
		Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate
				.opsForZSet().reverseRangeWithScores(key, start, stop);
		if (CollUtils.isEmpty(typedTuples)) {
			return CollUtils.emptyList();
		}
		return typedTuples.stream()
				.filter(t -> StringUtils.isNotBlank(t.getValue()))
				.map(t -> {
					PointsBoard board = new PointsBoard();
					Optional.ofNullable(t.getValue())
							.map(Long::valueOf)
							.ifPresent(board::setUserId);
					board.setPoints(t.getScore() != null ? t.getScore().intValue() : 0);
					board.setRank(start + 1);
					return board;
				})
				.peek(board -> board.setRank(board.getRank() + 1)) // 更新rank
				.collect(Collectors.toList());
	}
	
	/**
	 * 查询历史赛季 从db中查
	 *
	 * @param query 分页查询
	 * @return 历史赛季集合
	 */
	//TODO 暂时不做
	private List<PointsBoard> queryHistoryBoard(PointsBoardQuery query) {
		return null;
	}
	
	
	/**
	 * 查询历史赛季
	 *
	 * @param season 要查询的赛季
	 * @return PointsBoard
	 */
	private PointsBoard queryMyHistoryBoard(Long season) {
		//TODO 暂时不做
		return null;
	}
	
	/**
	 * 查询当前赛季的积分 从Redis查询
	 *
	 * @param key    在Redis中的KEY
	 * @param userId 当前登录用户的ID
	 * @return PointsBoard
	 */
	private PointsBoard queryMyCurrentBoard(String key, Long userId) {
		//1.获取分值
		Double score = redisTemplate.opsForZSet().score(key, userId.toString());
		//2.获取排名 从0开始所以需要加1
		Long rank = redisTemplate.opsForZSet().reverseRank(key, userId.toString());
		PointsBoard board = new PointsBoard();
		board.setRank(rank == null ? null : rank.intValue() + 1);
		board.setPoints(score == null ? null : score.intValue());
		return board;
	}
}
